/**
 * Copyright (c) 2015-2016, BruceZCQ (zcq@zhucongqi.cn).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.rlax.framework.config;

import java.lang.reflect.Method;

import com.alibaba.druid.filter.logging.Slf4jLogFilter;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.wall.WallFilter;
import com.jfinal.config.Constants;
import com.jfinal.config.Handlers;
import com.jfinal.config.Interceptors;
import com.jfinal.config.Plugins;
import com.jfinal.config.Routes;
import com.jfinal.core.Const;
import com.jfinal.ext.interceptor.POST;
import com.jfinal.ext.route.AutoBindRoutes;
import com.jfinal.ext2.handler.ActionExtentionHandler;
import com.jfinal.ext2.interceptor.NotFoundActionInterceptor;
import com.jfinal.ext2.kit.PageViewKit;
import com.jfinal.ext2.plugin.activerecord.generator.ModelGeneratorExt;
import com.jfinal.ext2.upload.filerenamepolicy.RandomFileRenamePolicy;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.activerecord.CaseInsensitiveContainerFactory;
import com.jfinal.plugin.activerecord.dialect.MysqlDialect;
import com.jfinal.plugin.activerecord.dialect.OracleDialect;
import com.jfinal.plugin.activerecord.generator.BaseModelGenerator;
import com.jfinal.plugin.activerecord.generator.Generator;
import com.jfinal.plugin.druid.DruidPlugin;
import com.jfinal.render.ViewType;
import com.jfinal.template.Engine;
import com.jfinal.upload.OreillyCos;
import com.rlax.framework.generator.GeMetaBuilder;
import com.rlax.framework.interceptor.BusinessExceptionInterceptor;

/**
 * @author Rlax
 *
 */
public abstract class AppBaseConfig extends com.jfinal.config.JFinalConfig {
	
	public final static String cfg = "cfg.txt";
	
	/** APP名称 */
	public static String APP_NAME = null;
	/** 是否执行代码自动生成 */
	protected boolean geRuned = true;
	
	/**
	 * Config other More constant
	 */
	public abstract void configMoreConstants(Constants me);
	
	/**
	 * Config other more route
	 */
	public abstract void configMoreRoutes(Routes me);
	
	/**
	 * Config other more plugin
	 */
	public abstract void configMorePlugins(Plugins me);
	
	/**
	 * Config other Tables Mapping
	 */
	public abstract void configTablesMapping(String configName, ActiveRecordPlugin arp);
	
	/**
	 * Config other more interceptor applied to all actions.
	 */
	public abstract void configMoreInterceptors(Interceptors me);
	
	/**
	 * Config other more handler
	 */
	public abstract void configMoreHandlers(Handlers me);

	/**
	 * Config other more Engine
	 * @param me
	 */
	public abstract void configMoreEngines(Engine me);
	
	/**
	 * After JFinalStarted
	 */
	public abstract void afterJFinalStarted();
	
	/**
	 * Config constant 配置常量
	 * 
	 * Default <br/>
	 * ViewType: JSP <br/>
	 * Encoding: UTF-8 <br/>
	 * ErrorPages: <br/>
	 * 404 : /WEB-INF/errorpages/404.jsp <br/>
	 * 500 : /WEB-INF/errorpages/500.jsp <br/>
	 * 403 : /WEB-INF/errorpages/403.jsp <br/>
	 * UploadedFileSaveDirectory : cfg basedir + appName <br/>
	 */
	public void configConstant(Constants me) {
		/** 默认视图 */
		me.setViewType(ViewType.JSP);
		/** 是否开发模式 */
		me.setDevMode(this.getAppDevMode());
		/** 默认编码 */
		me.setEncoding(Const.DEFAULT_ENCODING);
		/** 默认错误页面 */
		me.setError404View(PageViewKit.get404PageView());
		me.setError500View(PageViewKit.get500PageView());
		me.setError403View(PageViewKit.get403PageView());
		/** 默认上传文件路径 */
		me.setBaseUploadPath(this.getUploadPath());
		/** 默认上传下载路径 */
		me.setBaseDownloadPath(this.getDownloadPath());
		
		AppBaseConfig.APP_NAME = this.getAppName();
		/** 默认文件重命名策略-随机文件名 */
		OreillyCos.setFileRenamePolicy(new RandomFileRenamePolicy());
		// config others
		configMoreConstants(me);
	}
	
	/**
	 * Config route
	 * Config the AutoBindRoutes
	 * 自动bindRoute。controller命名为xxController。<br/>
	 * AutoBindRoutes自动取xxController对应的class的Controller之前的xx作为controllerKey(path)<br/>
	 * 如：MyUserController => myuser; UserController => user; UseradminController => useradmin<br/>
	 */
	public void configRoute(Routes me) {
		//me.add(new AutoBindRoutes());
		/** 根据注解自动扫描 */
		AutoBindRoutes abr = new AutoBindRoutes().autoScan(false);
		me.add(abr);
		// config others
		configMoreRoutes(me);
	}

	/**
	 * Config plugin
	 */
	public void configPlugin(Plugins me) {
		String[] dses = this.getDataSource();
		for (String ds : dses) {
			if (!this.getDbActiveState(ds)) {
				continue;
			}
			DruidPlugin drp = this.getDruidPlugin(ds);
			me.add(drp);
			ActiveRecordPlugin arp = this.getActiveRecordPlugin(ds, drp);
			me.add(arp);
			configTablesMapping(ds, arp);
		}
		// config others
		configMorePlugins(me);
	}
	
	/**
	 * Config interceptor applied to all actions.
	 */
	public void configInterceptor(Interceptors me) {
		// when action not found fire 404 error
		me.add(new NotFoundActionInterceptor());
		// add excetion interceptor
		me.add(new BusinessExceptionInterceptor());
		if (this.getHttpPostMethod()) {
			me.add(new POST());
		}
		// config others
		configMoreInterceptors(me);
	}
	
	/**
	 * Config handler
	 */
	public void configHandler(Handlers me) {
		// add extension handler
		me.add(new ActionExtentionHandler());
		// config others
		configMoreHandlers(me);
	}
	
	@Override
	public void configEngine(Engine arg0) {
		
	}
	
	public void afterJFinalStart() {
		super.afterJFinalStart();
		this.afterJFinalStarted();
	}

	private void loadPropertyFile() {
		if (this.prop == null) {
			this.loadPropertyFile(cfg);
		}
	}
	
	private boolean getHttpPostMethod() {
		this.loadPropertyFile();
		return this.getPropertyToBoolean("app.post",false);
	}

	private String getPath(String property) {
		if (StrKit.isBlank(property) || (!"downloads".equals(property) && !"uploads".equals(property))) {
			throw new IllegalArgumentException("property is invalid, property just use `downloads` or `uploads`");
		}
		this.loadPropertyFile();
		String app = this.getAppName();
		String baseDir = this.getProperty(String.format("app.%s.basedir", property));
		if (baseDir.endsWith("/")) {
			if (!baseDir.endsWith(property+"/")) {
				baseDir += (property+"/");	
			}
		}else{
			if (!baseDir.endsWith(property)) {
				baseDir += ("/"+property+"/");
			}else{
				baseDir += "/";
			}
		}
		return (new StringBuilder(baseDir).append(app).toString());
	}
	
	/**
	 * 获取File Upload Directory
	 * "/var/uploads/appname"
	 * @return
	 */
	private String getUploadPath(){
		return this.getPath("uploads");
	}
	
	/**
	 * 获取File Download Directory
	 * "/var/downloads/appname"
	 * @return
	 */
	private String getDownloadPath(){
		return this.getPath("downloads");
	}
	
	/**
	 * 获取app的dev mode
	 * @return
	 */
	private boolean getAppDevMode(){
		this.loadPropertyFile();
		return this.getPropertyToBoolean("app.dev", true);
	}

	/**
	 * 获取 AppName
	 * @return
	 */
	private String getAppName() {
		this.loadPropertyFile();
		String appName = this.getProperty("app.name");
		if (StrKit.isBlank(appName)) {
			throw new IllegalArgumentException("Please Set Your App Name in Your cfg file");
		}
		return appName;
	}
	
	private static final String ACTIVE_TEMPLATE = "db.%s.active";
	private static final String URL_MYSQL_TEMPLATE = "jdbc:%s://%s";
	private static final String URL_ORACLE_TEMPLATE = "jdbc:%s:thin:@%s";
	private static final String USER_TEMPLATE = "db.%s.user";
	private static final String PUBLIC_KEY_TEMPLATE = "db.%s.publickey";
	private static final String PASSWORD_TEMPLATE = "db.%s.password";
	private static final String INITSIZE_TEMPLATE = "db.%s.initsize";
	private static final String MAXSIZE_TEMPLATE = "db.%s.maxactive";
	private static final String DBTYPE_TEMPLATE = "db.%s.dbtype";
	private static final String SHOWSQL_TEMPLATE = "db.%s.showsql";
	private static final String USESYSTEM_TEMPLATE = "db.%s.usesystem";
	
	/**
	 * 获取是否打开数据库状态
	 * @return
	 */
	private boolean getDbActiveState(String ds){
		this.loadPropertyFile();
		return this.getPropertyToBoolean(String.format(ACTIVE_TEMPLATE, ds), false);
	}
	
	/**
	 * 获取数据源
	 * @return
	 */
	private String[] getDataSource() {
		this.loadPropertyFile();
		String ds = this.getProperty("db.ds");
		if (StrKit.isBlank(ds)) {
			return (new String[0]);
		}
		if (ds.contains("，")) {
			new IllegalArgumentException("Cannot use ，in ds");
		}
		return ds.split(",");
	}

	/**
	 * 获取数据源
	 * @return
	 */
	private String[] getProps(String key) {
		this.loadPropertyFile();
		String ds = this.getProperty(key);
		if (StrKit.isBlank(ds)) {
			return (new String[0]);
		}
		if (ds.contains("，")) {
			new IllegalArgumentException("Cannot use ，in ds");
		}
		return ds.split(",");
	}
	
	/**
	 * DruidPlugin
	 * @param prop ： property
	 * @return
	 */
	private DruidPlugin getDruidPlugin(String ds) {
		this.loadPropertyFile();
		String url = this.getProperty(String.format("db.%s.url", ds));
		if (this.getProperty(String.format(DBTYPE_TEMPLATE, ds)).toLowerCase().equals("mysql")) {
			url = String.format(URL_MYSQL_TEMPLATE, this.getProperty(String.format(DBTYPE_TEMPLATE, ds)), url);
			if (!url.endsWith("?characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull")) {
				url += "?characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull";
			}
		} else if (this.getProperty(String.format(DBTYPE_TEMPLATE, ds)).toLowerCase().equals("oracle")) {
			url = String.format(URL_ORACLE_TEMPLATE, this.getProperty(String.format(DBTYPE_TEMPLATE, ds)), url);
		}

		DruidPlugin dp = new DruidPlugin(url,
				this.getProperty(String.format(USER_TEMPLATE, ds)),
				this.getProperty(String.format(PASSWORD_TEMPLATE, ds)));
		dp.setInitialSize(this.getPropertyToInt(String.format(INITSIZE_TEMPLATE, ds)));
		dp.setMaxActive(this.getPropertyToInt(String.format(MAXSIZE_TEMPLATE, ds)));
		// SQL注入，SQL防火墙，数据库加密
		dp.setFilters("config");
		
		Slf4jLogFilter logFilter = new Slf4jLogFilter();
		StatFilter statFilter = new StatFilter();

		logFilter.setConnectionLogEnabled(false);
		logFilter.setStatementLogEnabled(false);
		logFilter.setStatementExecutableSqlLogEnable(true);
		logFilter.setResultSetLogEnabled(false);
		dp.addFilter(logFilter);
		
		// 慢sql记录
		statFilter.setSlowSqlMillis(3 * 1000);
		statFilter.setLogSlowSql(true);
		dp.addFilter(statFilter);
		
		WallFilter wall = new WallFilter();
		wall.setDbType(this.getProperty(String.format(DBTYPE_TEMPLATE, ds)));
		dp.addFilter(wall);
		
		dp.setPublicKey(this.getProperty(String.format(PUBLIC_KEY_TEMPLATE, ds)));
		if (this.getProperty(String.format(DBTYPE_TEMPLATE, ds)).toLowerCase().equals("oracle")) {
			dp.setValidationQuery("select 1 from dual");
		}
		
		/** 代码生成 */
		if (!this.geRuned) {
			dp.start();
			System.out.println("ge ds : " + ds);
			BaseModelGenerator baseGe = new BaseModelGenerator(this.getBaseModelPackage(ds), this.getBaseModelOutDir(ds));
			ModelGeneratorExt modelGe = new ModelGeneratorExt(this.getModelPackage(ds), this.getBaseModelPackage(ds), this.getModelOutDir(ds));
			GeMetaBuilder metaBuilder = new GeMetaBuilder(dp.getDataSource());
			/** 跳过表生成的前缀 */
			metaBuilder.setSkipPre(getProps(String.format(EXCLUDEDTABLE_PRE_TEMPLATE, ds)));
			Generator ge = new Generator(dp.getDataSource(), baseGe, modelGe);
			ge.setMetaBuilder(metaBuilder);
			ge.setGenerateDataDictionary(this.getGeDictionary(ds));
			
			// 设置数据库方言
			if (this.getProperty(String.format(DBTYPE_TEMPLATE, ds)).equals("oracle")) {
				ge.setDialect(new OracleDialect());
			} else {
				ge.setDialect(new MysqlDialect());
			}
			// 添加不需要生成的表名
			ge.addExcludedTable(getProps(String.format(EXCLUDEDTABLE_TEMPLATE, ds)));
			// 设置是否在 Model 中生成 dao 对象
			ge.setGenerateDaoInModel(true);
			// 设置需要被移除的表名前缀用于生成modelName。例如表名 "osc_user"，移除前缀 "osc_"后生成的model名为 "User"而非 OscUser
			ge.setRemovedTableNamePrefixes(getProps(String.format(REMOVEDTABLENAMEPREFIXES_TEMPLATE, ds)));
			// 生成
			ge.generate();
		}
		return dp;
	}
	
	/**
	 * 获取ActiveRecordPlugin 
	 * @param dp DruidPlugin
	 * @return
	 */
	private ActiveRecordPlugin getActiveRecordPlugin(String ds, DruidPlugin dp){
		this.loadPropertyFile();
		ActiveRecordPlugin arp = new ActiveRecordPlugin(ds, dp);
		arp.setShowSql(this.getPropertyToBoolean(String.format(SHOWSQL_TEMPLATE, ds)));
		
		if (this.getProperty(String.format(DBTYPE_TEMPLATE, ds)).toLowerCase().equals("mysql")) {
			arp.setDialect(new MysqlDialect());
		} else if (this.getProperty(String.format(DBTYPE_TEMPLATE, ds)).toLowerCase().equals("oracle")) {
			arp.setDialect(new OracleDialect());
			arp.setContainerFactory(new CaseInsensitiveContainerFactory());//忽略大小写
		}
		
		// mapping
		try {
			Class<?> clazz = Class.forName(this.getModelPackage(ds)+"._MappingKit");
			Method mapping = clazz.getMethod("mapping", ActiveRecordPlugin.class);
			mapping.invoke(clazz, arp);
			
			// 是否添加系统 Mapping
			if (this.getPropertyToBoolean(String.format(USESYSTEM_TEMPLATE, ds), false)) {
				com.rlax.web.model._MappingKit.mapping(arp);
			}
		} catch (Exception e) {
			try {
				throw e;
			} catch (Exception e1) {
				throw (new RuntimeException(String.valueOf(e1)));
			}
		}
		return arp;
	}
	
	private static final String DICT_TEMPLATE = "ge.%s.dict";
	private static final String BASE_MODEL_OUTDIR_TEMPLATE = "ge.%s.base.model.outdir";
	private static final String BASE_MODEL_PACKAGE_TEMPLATE = "ge.%s.base.model.package";
	private static final String MODEL_OUTDIR_TEMPLATE = "ge.%s.model.outdir";
	private static final String MODEL_PACKAGE_TEMPLATE = "ge.%s.model.package";
	private static final String REMOVEDTABLENAMEPREFIXES_TEMPLATE = "ge.%s.removedtablenameprefixes";
	private static final String EXCLUDEDTABLE_TEMPLATE = "ge.%s.excludedtable";
	private static final String EXCLUDEDTABLE_PRE_TEMPLATE = "ge.%s.excludedtableprefixes";

	
	private boolean getGeDictionary(String ds) {
		this.loadPropertyFile();
		return this.getPropertyToBoolean(String.format(DICT_TEMPLATE, ds), false);
	}
	
	private String getBaseModelOutDir(String ds) {
		this.loadPropertyFile();
		String name = this.getProperty(String.format(BASE_MODEL_OUTDIR_TEMPLATE, ds));
		
		if (StrKit.isBlank(name)) {
			throw new IllegalArgumentException("Please set your base model outdir in cfg.txt file");
		}
		
		return name;
	}
	
	private String getBaseModelPackage(String ds) {
		this.loadPropertyFile();
		String name = this.getProperty(String.format(BASE_MODEL_PACKAGE_TEMPLATE, ds));
		
		if (StrKit.isBlank(name)) {
			throw new IllegalArgumentException("Please set your base model package in cfg.txt file");
		}
		
		return name;
	}
	
	private String getModelOutDir(String ds) {
		this.loadPropertyFile();
		String name = this.getProperty(String.format(MODEL_OUTDIR_TEMPLATE, ds));
		
		if (StrKit.isBlank(name)) {
			throw new IllegalArgumentException("Please set your model outdir in cfg.txt file");
		}
		
		return name;
	}
	
	private String getModelPackage(String ds) {
		this.loadPropertyFile();
		String name = this.getProperty(String.format(MODEL_PACKAGE_TEMPLATE, ds));
		
		if (StrKit.isBlank(name)) {
			throw new IllegalArgumentException("Please set your model package in cfg.txt file");
		}
		
		return name;
	}
}
